package com.juma.cartridge.toker;

import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.util.Pool;

/**
 * token池:用于token缓冲,避免每次produce时都与中间件交互,影响调用端性能.
 * @ClassName TokenPool
 * @Description
 * @Author zhuangguangyou
 * @Date 2019年11月26日 下午1:50:58
 * @Version 1.0
 *
 */
@Slf4j
public class TokenPool {
    
    private Map<String,TreeMap<Integer,BlockingQueue<Token>>> pools; 
    private ExecutorService loadExecutor;
    private Pool<Jedis> jedisPool;
    private int cacheSize;
    
    public TokenPool(Pool<Jedis> pool,int cacheSize) {
        this.jedisPool = pool;
        this.cacheSize = cacheSize;
        this.pools = new ConcurrentHashMap<String, TreeMap<Integer,BlockingQueue<Token>>>();
        this.loadExecutor = new ThreadPoolExecutor(1, 1,0L,TimeUnit.MILLISECONDS,new SynchronousQueue<Runnable>(),new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                return new Thread("Toker-tokenPool");
            }
        });
    }
    
    public Token get(String name,int timeoutInSec) {
        
        String branch = (name==null||name.isEmpty())?"default":name; 
        TreeMap<Integer,BlockingQueue<Token>> tree = pools.get(branch);
        //初始化
        if (tree==null) {
            synchronized (this) {
                tree = pools.get(branch);
                if(tree==null) {
                    tree = new TreeMap<Integer,BlockingQueue<Token>>();
                    pools.put(branch, tree);    
                }
                    
            }
        }
        
        Entry<Integer,BlockingQueue<Token>> entry = tree.ceilingEntry(Integer.valueOf(timeoutInSec));
        if(entry==null) {
            synchronized (this) {
                entry = tree.ceilingEntry(Integer.valueOf(timeoutInSec));
                if(entry==null) {
                    BlockingQueue<Token> que = new LinkedBlockingQueue<Token>();
                    tree.put(Integer.valueOf(timeoutInSec), que);
                    entry = tree.ceilingEntry(Integer.valueOf(timeoutInSec));
                }
            }
        }
        
        
        Token token = null;
        BlockingQueue<Token> que = entry.getValue();
        try {
            token = que.poll(0,TimeUnit.MILLISECONDS);    
        }catch(Exception e) {}
        
        if(token==null) {
            loadToken(name,timeoutInSec,que);
        }
        return token;
    }
    
    
    /**
     * 加载一批token进行缓存
     * @param name
     * @param timeoutInSec
     * @param que
     */
    private void loadToken(final String name,final int timeoutInSec, final BlockingQueue<Token> que) {

        final Runnable task = new Runnable(){

            @Override
            public void run() {
                log.debug("开始token预生成[cacheSize={},name={},timeoutInSec={}]",cacheSize,name,timeoutInSec);
                //生成cacheSize个token
                for(int i=0;i<cacheSize;i++) {
                    String id = UUID.randomUUID().toString().replaceAll("-", "");
                    Token tk = new Token(name,id,timeoutInSec);
                    try {
                        ApplyUtils.apply(jedisPool, tk);
                        que.add(tk);
                    }catch(Exception e) {
                        log.error("token预生成异常:"+e.getMessage(),e.getCause());
                    }
                }
                log.debug("token预生成完成[cacheSize={},name={},timeoutInSec={}]",cacheSize,name,timeoutInSec);
            }
        };
        
        try {
            loadExecutor.execute(task);    
        }catch(Exception e){}
        
    }
    
}
